// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2020, LomboTech Co.Ltd.
 * Author: lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 */
#include <linux/of.h>
#include <linux/clk.h>
#include <mach/csp.h>
#include <../regulator.h>

#define I2C_SLAVE_ADDRESS	0x30
#define I2C_SLAVE_REG_LEN	0x1
#define I2C_SLAVE_ADDR_BITS	0
#define I2C_SOURCE_FREQUENCY    24000000
#define I2C_FS_BAUDRATE 200000
#define I2C_FS_SPK_LEN  4
#define I2C_HS_SPK_LEN  2
#define I2C_INT_ALL_ERROR   0x00300f08
#define I2C_AUTO_FINISHE_MASK   0x00000002

static reg_i2c9_t *regbase;
struct clk *clk_gat, *clk_reset;
struct clk *clk_osc24m, *a_hf_clk, *i2c_mod;

static void __i2c_enable(int n_or_e)
{
	reg_i2c9_enable_t renable;

	renable.val = readl((void __iomem *)&(regbase->enable));
	renable.bits.en = n_or_e;
	writel(renable.val, (void __iomem *)&(regbase->enable));

	while (1) {
		renable.val = readl((void __iomem *)&(regbase->enable));
		if (renable.bits.en == n_or_e)
			break;
	}
}

static int __i2c_wait_auto_finished(void)
{
	reg_i2c9_int_pd_t int_pd;

	while (1) {
		/* check error */
		int_pd.val = readl((void __iomem *)&(regbase->int_pd));

		if ((int_pd.val & I2C_INT_ALL_ERROR) != 0) {
			pr_err("tx erro, i2c pending: %x\n", int_pd.val);
			return 1;
		}

		if (int_pd.val & I2C_AUTO_FINISHE_MASK)
			break;
	}

	return 0;
}

int slave_device_write(void *context, unsigned int reg, unsigned int val)
{
	unsigned char rar;
	reg_i2c9_len_t rlen;
	reg_i2c9_ctl_t rctl;
	reg_i2c9_enable_t renable;
	reg_i2c9_fifo_status_t rfifo_status;
	reg_i2c9_data_cmd_t rdata_cmd;

	/* clear all int */
	writel(0xffffffff, (void __iomem *)&(regbase->tx_abt_source));
	writel(0xffffffff, (void __iomem *)&(regbase->int_clr));

	/* set register address */
	writel(reg, (void __iomem *)&(regbase->rar));

	/* set data length */
	rlen.val = 0;
	rlen.bits.tx_len = 1;
	rlen.bits.rx_len = 0;
	writel(rlen.val, (void __iomem *)&(regbase->len));

	/* set auto dir = write:0 */
	rctl.val = readl((void __iomem *)&(regbase->ctl));
	rctl.bits.auto_dir = 0;
	writel(rctl.val, (void __iomem *)&(regbase->ctl));

	/* enable i2c */
	__i2c_enable(1);

	/* send data */
	rfifo_status.val = readl((void __iomem *)&(regbase->fifo_status));
	if (rfifo_status.bits.tx_full == 0) {
		rdata_cmd.bits.data = val;
		writel(rdata_cmd.val, (void __iomem *)&(regbase->data_cmd));
	} else {
		__i2c_enable(0);
		pr_err("i2c tx fifo full\n");
		return -EFAULT;
	}

	/* set auto transimit start */
	renable.val = readl((void __iomem *)&(regbase->enable));
	renable.bits.auto_start = 1;
	writel(renable.val, (void __iomem *)&(regbase->enable));

	/* wait auto finished */
	rar = __i2c_wait_auto_finished();
	if (rar) {
		__i2c_enable(0);
		return -EFAULT;
	}

	/* check fifo status */
	rfifo_status.val = readl((void __iomem *)&(regbase->fifo_status));
	if (rfifo_status.bits.tx_fl != 0) {
		__i2c_enable(0);
		pr_err("%s:%d, i2c tx fifo not empty after tx finish\n",
			__func__, __LINE__);
		return -EFAULT;
	}

	__i2c_enable(0);

	return 0;
}

int slave_device_read(void *context, unsigned int reg, unsigned int *val)
{
	int rar;
	reg_i2c9_len_t rlen;
	reg_i2c9_ctl_t rctl;
	reg_i2c9_enable_t renable;
	reg_i2c9_fifo_status_t rfifo_status;
	reg_i2c9_data_cmd_t rdata_cmd;

	/* clear all int */
	writel(0xffffffff, (void __iomem *)&(regbase->tx_abt_source));
	writel(0xffffffff, (void __iomem *)&(regbase->int_clr));

	/* set register address */
	writel(reg, (void __iomem *)&(regbase->rar));

	/* set data length */
	rlen.val = 0;
	rlen.bits.tx_len = 0;
	rlen.bits.rx_len = 1;
	writel(rlen.val, (void __iomem *)&(regbase->len));

	/* set auto dir = read:1 */
	rctl.val = readl((void __iomem *)&(regbase->ctl));
	rctl.bits.auto_dir = 1;
	writel(rctl.val, (void __iomem *)&(regbase->ctl));

	/* enable i2c */
	__i2c_enable(1);
	/* set auto transimit start */
	renable.val = readl((void __iomem *)&(regbase->enable));
	renable.bits.auto_start = 1;
	writel(renable.val, (void __iomem *)&(regbase->enable));

	/* wait auto finished */
	rar = __i2c_wait_auto_finished();
	if (rar) {
		__i2c_enable(0);
		return -EFAULT;
	}

	rfifo_status.val = readl((void __iomem *)&(regbase->fifo_status));
	if (rfifo_status.bits.rx_empty) {
		__i2c_enable(0);
		pr_err("%s:%d, rx fifo empty after auto finished\n",
			__func__, __LINE__);
		return -EFAULT;
	}

	/* read data */
	rdata_cmd.val = readl((void __iomem *)&(regbase->data_cmd));
	*val = rdata_cmd.bits.data;

	rfifo_status.val = readl((void __iomem *)&(regbase->fifo_status));
	if (rfifo_status.bits.rx_fl != 0) {
		__i2c_enable(0);
		pr_err("%s:%d,i2c rx fifo not empty after rx finish\n",
			__func__, __LINE__);
		return -EFAULT;
	}

	__i2c_enable(0);
	return 0;
}

static int __i2c_controller_init(void)
{
	reg_i2c9_fifo_ctl_t fifo_ctl;
	reg_i2c9_ctl_t ctl;
	reg_i2c9_dar_t dar;
	reg_i2c9_ss_div_t ss_div;
	reg_i2c9_spk_len_t spk_len;
	reg_i2c9_sda_timing_t sda_timing;
	u32 half_cycle_cnt;
	u32 h_cnt;
	u32 l_cnt;
	u32 tx_hold, rx_hold, setup;

	/* reset fifo */
	fifo_ctl.val = readl((void __iomem *)&(regbase->fifo_ctl));
	fifo_ctl.bits.txrst = 1;
	fifo_ctl.bits.rxrst = 1;
	writel(fifo_ctl.val, (void __iomem *)&(regbase->fifo_ctl));

	ctl.val = readl((void __iomem *)&(regbase->ctl));
	ctl.bits.auto_en = 0;
	ctl.bits.hs_pp_en = 0;
	/* speed select -- > fast mode, 200k */
	ctl.bits.speed = 1;
	writel(ctl.val, (void __iomem *)&(regbase->ctl));

	/* set baudrate = 200K */
	half_cycle_cnt = I2C_SOURCE_FREQUENCY / I2C_FS_BAUDRATE / 2;
	h_cnt = half_cycle_cnt - (I2C_FS_SPK_LEN + 3);
	l_cnt = half_cycle_cnt;
	ss_div.bits.lcnt = l_cnt;
	ss_div.bits.hcnt = h_cnt;
	writel(ss_div.val, (void __iomem *)&(regbase->ss_div));

	/* set spike len */
	spk_len.bits.ss_spklen = I2C_FS_SPK_LEN;
	spk_len.bits.hs_spklen = I2C_HS_SPK_LEN;
	writel(spk_len.val, (void __iomem *)&(regbase->spk_len));

	/* set hold time and setup time */
	tx_hold = half_cycle_cnt / 2;
	rx_hold = half_cycle_cnt / 2;
	setup = half_cycle_cnt / 2;
	sda_timing.bits.sda_tx_hold = tx_hold;
	sda_timing.bits.sda_rx_hold = rx_hold;
	sda_timing.bits.sda_setup = setup;
	writel(sda_timing.val, (void __iomem *)&(regbase->sda_timing));

	/* set slave addr */
	dar.val = readl((void __iomem *)&(regbase->dar));
	dar.bits.dar = I2C_SLAVE_ADDRESS;
	writel(dar.val, (void __iomem *)&(regbase->dar));

	/* set slave reg_len */
	ctl.bits.auto_rar_len = I2C_SLAVE_REG_LEN;
	writel(ctl.val, (void __iomem *)&(regbase->ctl));

	/* set dev addr len = 7bits */
	ctl.bits.dev_addr_len = I2C_SLAVE_ADDR_BITS;
	writel(ctl.val, (void __iomem *)&(regbase->ctl));

	/*set auto transmit mode */
	ctl.bits.auto_en = 1;
	writel(ctl.val, (void __iomem *)&(regbase->ctl));

	return 0;
}

int __pmic_init(struct platform_device *pdev)
{
	int ret;
	struct resource *res;

	/* check if we use the amu */
	if (of_property_read_bool(pdev->dev.of_node, "use_amu"))
		return 0;

	pmic_regmap.reg_read = &slave_device_read;
	pmic_regmap.reg_write = &slave_device_write;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		pr_err("%s:%d get resource error\n", __func__, __LINE__);
		return -EINVAL;
	}

	regbase = (reg_i2c9_t *)devm_ioremap_resource(&pdev->dev, res);
	if (regbase < 0) {
		pr_err("%s:%d, remap register error\n", __func__, __LINE__);
		return -EINVAL;
	}

	clk_gat = devm_clk_get(&pdev->dev, "a_apb_i2c9_gate");
	if (IS_ERR_OR_NULL(clk_gat)) {
		pr_err("%s:%d, get clk gate error\n", __func__, __LINE__);
		return -EINVAL;
	}
	clk_prepare_enable(clk_gat);

	clk_reset = devm_clk_get(&pdev->dev, "a_apb_i2c9_reset");
	if (IS_ERR_OR_NULL(clk_reset)) {
		pr_err("%s:%d, get clk reset error\n", __func__, __LINE__);
		return -EINVAL;
	}
	clk_prepare_enable(clk_reset);

	a_hf_clk = devm_clk_get(&pdev->dev, "a_hf_clk");
	if (IS_ERR_OR_NULL(a_hf_clk)) {
		pr_err("%s:%d, get a_hf_clk error\n", __func__, __LINE__);
		return -EINVAL;
	}

	clk_osc24m = devm_clk_get(&pdev->dev, "osc24m");
	if (IS_ERR_OR_NULL(clk_osc24m)) {
		pr_err("%s:%d, get osc24m error\n", __func__, __LINE__);
		return -EINVAL;
	}
	ret = clk_set_parent(a_hf_clk, clk_osc24m);

	i2c_mod = devm_clk_get(&pdev->dev, "i2c9_clk");
	if (IS_ERR_OR_NULL(i2c_mod)) {
		pr_err("%s:%d, get i2c9 clk error\n", __func__, __LINE__);
		return -EINVAL;
	}

	ret = clk_set_parent(i2c_mod, a_hf_clk);

	clk_prepare_enable(i2c_mod);

	/* initialize the i2c9 conctroller */
	__i2c_controller_init();

	return 0;
}
